package com.thiswhyme.redis.geo;

import com.thiswhyme.redis.geo.entity.GeoMember;
import lombok.AllArgsConstructor;
import org.redisson.api.GeoEntry;
import org.redisson.api.GeoOrder;
import org.redisson.api.GeoUnit;
import org.redisson.api.RGeo;
import org.redisson.api.RedissonClient;
import org.redisson.api.geo.GeoSearchArgs;
import org.redisson.api.geo.OptionalGeoSearch;
import org.springframework.data.geo.Metrics;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Geo 模版方法 - Redisson 实现
 *
 * @author thiswhyme
 * @date 2022/11/4
 */
@AllArgsConstructor
public class RedissonGeoTemplate extends AbstractGeoTemplate {

    private final RedissonClient redissonClient;

    @Override
    public <T> Long add(String key, GeoMember<T> geoMember) {
        RGeo<T> geo = redissonClient.getGeo(key);
        return geo.add(geoMember.getLongitude(), geoMember.getLatitude(), geoMember.getMember());
    }

    @Override
    public <T> Long batchAdd(String key, List<GeoMember<T>> geoMembers) {
        if (CollectionUtils.isEmpty(geoMembers)) {
            return 0L;
        }
        RGeo<T> geo = redissonClient.getGeo(key);
        return geo.add(geoMembersToGeoEntries(geoMembers).toArray(new GeoEntry[0]));
    }

    @Override
    public <T> boolean remove(String key, T member) {
        RGeo<T> geo = redissonClient.getGeo(key);
        return geo.remove(member);
    }

    @Override
    public boolean clear(String key) {
        redissonClient.getGeo(key).clear();
        return true;
    }

    @Override
    public <T> Double distance(String key, T fromMember, T toMember, Metrics metric) {
        RGeo<T> geo = redissonClient.getGeo(key);
        return geo.dist(fromMember, toMember, metricsToGeoUnit(metric));
    }

    @Override
    public <T> Map<T, Double> radius(String key, double lng, double lat, double radius, Metrics metric, Boolean asc, Integer count) {
        RGeo<T> geo = redissonClient.getGeo(key);
        OptionalGeoSearch args = GeoSearchArgs.from(lng, lat)
                .radius(radius, metricsToGeoUnit(metric));
        if (null != asc) {
            args.order(asc ? GeoOrder.ASC : GeoOrder.DESC);
        }
        if (null != count) {
            args.count(count);
        }
        return geo.searchWithDistance(args);
    }

    @Override
    public <T> Map<T, Double> radius(String key, T member, double radius, Metrics metric, Boolean asc, Integer count) {
        RGeo<T> geo = redissonClient.getGeo(key);
        OptionalGeoSearch args = GeoSearchArgs.from(member)
                .radius(radius, metricsToGeoUnit(metric));
        if (null != asc) {
            args.order(asc ? GeoOrder.ASC : GeoOrder.DESC);
        }
        if (null != count) {
            args.count(count);
        }
        return geo.searchWithDistance(args);
    }

    /**
     * GeoMember 转 GeoLocation
     * @param geoMember
     * @return
     * @param <T>
     */
    private <T> GeoEntry geoMemberToGeoEntry(GeoMember<T> geoMember) {
        return new GeoEntry(geoMember.getLongitude(), geoMember.getLatitude(), geoMember.getMember());
    }

    /**
     * GeoMember 列表转 GeoLocation 列表
     *
     * @param geoMembers
     * @return
     * @param <T>
     */
    private <T> List<GeoEntry> geoMembersToGeoEntries(List<GeoMember<T>> geoMembers) {
        List<GeoEntry> results = new ArrayList<>();
        geoMembers.forEach(geoMember -> results.add(geoMemberToGeoEntry(geoMember)));
        return results;
    }

    /**
     * Metricss 转 GeoUnit
     *
     * @param metric
     * @return
     */
    private GeoUnit metricsToGeoUnit(Metrics metric) {
        switch (metric) {
            case KILOMETERS: 
                return GeoUnit.KILOMETERS;
            case MILES:
                return GeoUnit.MILES;
            default:
                return GeoUnit.METERS;
        }
    }
}

